<template>
  <div v-if="editor" class="tx">
    <TXHeader
      ref="header"
      :editor="editor"
      :handleImageUpload="handle_image_url"
      :handleVideoUpload="handle_video_url"
    />
    <floating-menu-comp :editor="editor" :tippy-options="{ duration: 300 }" v-if="editor">
    </floating-menu-comp>
    <bubble-menu-comp
      :editor="editor"
      :handleImageUpload="handle_image_url"
      :handleVideoUpload="handle_video_url"
    />
    <div class="tx-container" :class="resizeClass">
      <editor-content :editor="editor" />
    </div>
  </div>
</template>

<script>
// core
import { Editor, EditorContent, FloatingMenu as FloatingMenuComp } from '@tiptap/vue-3'
// extensions
import StarterKit from '@tiptap/starter-kit'
import Image from './extension/Image/index.js'
import UnderLine from '@tiptap/extension-underline'
import Color from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'
import CharacterCount from '@tiptap/extension-character-count'
import FloatingMenu from '@tiptap/extension-floating-menu'
import Focus from '@tiptap/extension-focus'
import FontFamily from '@tiptap/extension-font-family'
import Placeholder from '@tiptap/extension-placeholder'
import TextAlign from './extension/TextAlign/index.js'
import FirstIndent from "./extension/FirstIndent/index.js"
import Typography from '@tiptap/extension-typography'
import Highlight from '@tiptap/extension-highlight'
import Link from '@tiptap/extension-link'
import Subscript from '@tiptap/extension-subscript'
import Superscript from '@tiptap/extension-superscript'
import Table from "./extension/extension-table"
import TableCell from './extension/extension-table-cell'
import TableHeader from './extension/extension-table-header'
import TableRow from './extension/extension-table-row'
import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'
import FontSize from './extension/FontSize/index.js'
import Video from './extension/Video/index.js'
import Indent from "./extension/Indent/index.js"
// components
import TXHeader from './components/TXHeader/index.vue'
import BubbleMenuComp from './components/BubbleMenu/index.vue'
// css
import './source/icon/iconfont.css'
import 'katex/dist/katex.min.css'
// utils
import {
  getBase64,
  extractImageDataFromRtf,
  replaceImagesFileSourceWithInlineRepresentation,
  getSelectedTextSize,
  getLocalTemplateUrl, base64ImgtoFile
} from "./utils/index.js"
import Commands from './utils/handler.js'
export default {
  name: 'TiptapEditor',
  components: {
    EditorContent,
    BubbleMenuComp,
    FloatingMenuComp,
    TXHeader
  },
  props: {
    value: {
      type: String,
      default: '',
    },
    readonly: {
      type: Boolean,
      default: false
    },
    resize: {
      type: Boolean,
      default: true
    },
    /**
     * @type { 'horizontal' | 'vertical' | 'both' }
     * @default 'vertical'
     */
    resizeType: {
      type: String,
      default: 'vertical'
    },
    handle_image_url: {
      type: Function,
      default: null
    },
    handle_video_url: {
      type: Function,
      default: null
    }
  },
  data() {
    return {
      editor: null,
      fontSize: '16px',
      commands: new Commands(this.editor)
    }
  },
  watch: {
    value(value) {
      // HTML
      const isSame = this.editor.getHTML() === value
      // JSON
      // const isSame = JSON.stringify(this.editor.getJSON()) === JSON.stringify(value)
      if (isSame) {
        return
      }
      this.editor.commands.setContent(value, false)
    },
  },
  computed: {
    resizeClass () {
      if (this.resize) {
        return {
          'tx-resize-x': this.resizeType === 'horizontal',
          'tx-resize-y': this.resizeType === 'vertical',
          'tx-resize-both': this.resizeType === 'both'
        }
      }
      return  {}
    }
  },
  mounted() {
    this.editor = new Editor({
      content: this.value,
      injectCSS: true,
      autofocus: 'end',
      editable: !this.readonly,
      extensions: [
        Color.configure({
          types: ['textStyle'],
        }),
        TextStyle,
        FontSize,
        UnderLine,
        // BubbleMenu.configure({
        //   element: document.querySelector('.menu'),
        // }),
        CharacterCount,
        FontFamily,
        Indent,
        FirstIndent,
        Focus.configure({
          mode: 'shallowest',
          className: 'focused'
        }),
        StarterKit.configure({
          paragraph: {
            HTMLAttributes: {
              class: 'tiptap-p'
            }
          },
          history: {
            limit: 50
          },
          dropcursor: {
            color: '#ff0000',
            width: 2
          }
        }),
        Image.configure({
          inline: true,
          allowBase64: true,
          HTMLAttributes: {
            class: 'image'
          }
        }),
        Placeholder.configure({
          placeholder: 'Write something …',
        }),
        TextAlign.configure({
          types: ['heading', 'paragraph'],
        }),
        Typography,
        Highlight.configure({
          multicolor: true,
        }),
        Link,
        Subscript,
        Superscript,
        Table.configure({
          resizable: true,
          allowTableNodeSelection: true,
          cellMinWidth: 60
        }),
        TableCell.configure({
          HTMLAttributes: {
            style: 'border: 1px solid #ccc;min-width: 25px;'
          }
        }),
        TableHeader.configure({
          HTMLAttributes: {
            style: 'border: 1px solid #ccc;'
          }
        }),
        TableRow.configure({
          HTMLAttributes: {
            // style: 'border: 1px solid #ccc;'
          }
        }),
        TaskItem,
        TaskList,
        Video,
        FloatingMenu
      ],
      onCreate: () => {
        this.editor.view.dom.addEventListener('paste', this.handlePaste)
        this.editor.view.dom.addEventListener('click', () => {
          // console.log(this.editor.state.selection);
          // console.log(this.editor.state.selection.$from.node());
        })
      },
      onUpdate: () => {
        // HTML
        this.$emit('input', this.editor.getHTML())
        // JSON
        // this.$emit('input', this.editor.getJSON())
      },
      onSelectionUpdate: () => {
        this.$refs.header.setSelectFontSize(getSelectedTextSize(this.editor) || '16px')
      },
      onDestroy: () => {
        this.editor.view.dom.removeEventListener('paste', this.handlePaste)
      }
    })
  },
  beforeDestroy() {
    this.editor.destroy()
  },
  methods: {
    // 剪切板内容处理
    async handlePaste (event) {
      event.preventDefault()
      const clipData = await event.clipboardData;
      const { items, files } = clipData
      if (files.length > 0) {
        // 获取文件
        for (const file of files) {
          try {
            // 图片
            if (file.type.startsWith('image/')) {
              // 创建临时地址
              const localUrl = await getLocalTemplateUrl(await getBase64(file))
              // 上传图片
              if (this.handle_image_url) {
                const networkUrl = await this.handle_image_url(file)
                if (networkUrl) {
                  URL.revokeObjectURL(localUrl) // 释放临时地址
                  this.editor.commands.setImage({ src: networkUrl })
                }
              } else {
                this.editor.commands.setImage({ src: localUrl })
              }
            } else if (file.type.startsWith('video/')) {
              // 创建临时地址
              const localUrl = URL.createObjectURL(file)
              // const { width, height } = await getVideoDimensions(localUrl)
              // 上传视频
              if (this.handle_video_url) {
                const networkUrl = await this.handle_video_url(file)
                let content = this.editor.getHTML()
                URL.revokeObjectURL(localUrl) // 释放临时地址
                content.replace(localUrl,networkUrl)
              } else {
                this.editor.commands.setVideo({ src: localUrl, width: '100%', height: '100%' })
              }
            }
          } catch (e) {
            console.log(e)
          }
        }
      } else {
        const rtf = clipData.getData('text/rtf')
        // const plain = clipData.getData('text/plain')
        const html = clipData.getData('text/html')
        if (rtf) {
          let content = this.editor.getHTML()
          const images = Array.from(content.matchAll(/<img[^>]*src="((file:\/\/\/)*[^"]*)"[^>]*>/g), match => match[1])
          const base64List = replaceImagesFileSourceWithInlineRepresentation(images, extractImageDataFromRtf(rtf))
          let localUrlList = []
          if (this.handle_image_url) {
            const FileList = base64List.map(base64 => base64ImgtoFile(base64))
            const PromiseList = FileList.map(file => this.handle_image_url(file))
            localUrlList = await Promise.all(PromiseList)
          } else {
            localUrlList = base64List.map(async base64 => {
              return await getLocalTemplateUrl(base64)
            })
          }
          for (const image of images) {
            const index = images.indexOf(image);
            const url = await localUrlList[index]
            content = content.replace(image, url)
          }
          this.editor.commands.setContent(content)
        }
      }
    }
  }
}
</script>

<style lang="less" scoped>
@import './css/index.less';
</style>